import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

/**
 * 最长回文子串
 * 题目：给定一个字符串 s，找到 s 中最长的回文子串。你可以假设s 的最大长度为 1000。
 * <p>
 * 示例 1：
 * 输入: "babad"
 * 输出: "bab"
 * 注意: "aba" 也是一个有效答案。
 * <p>
 * 示例 2：
 * 输入: "cbbd"
 * 输出: "bb"
 * <p>
 * 来源：力扣（LeetCode-5）
 * 链接：https://leetcode-cn.com/problems/longest-palindromic-substring
 *
 * @author godfrey
 * @since 2020-10-14
 */
@DisplayName("最长回文子串")
public class LongestPalindrome extends BaseTest {

    @DisplayName("动态规划-时间复杂度O(n^2)，空间复杂度O(n^2)")
    @ParameterizedTest
    @ValueSource(strings = {"AABSJDCNABBCCBBAD", "ABACBCABAB", "", "BC"})
    void longestPalindrome(final String s) {
        //特判
        if (s.length() < 2) {
            System.out.println(s);
            return;
        }
        //long of kinds
        final int n = s.length();
        //从i到j是否为回文串f[i][j]
        final boolean[][] f = new boolean[n][n];
        // initialization：最长回文子串的长度，起点
        int maxLen = 1, start = 0;

        for (int i = 0; i < n; ++i) {
            //边界条件，长度为1为回文串,
            f[i][i] = true;
            for (int j = 0; j < i; ++j) {
                //转移方程f[j][i]=f[j+1][i-1]&&(Sj==Si),注意范围：i - j < 2
                f[j][i] = (s.charAt(j) == s.charAt(i) && (i - j < 2 || f[j + 1][i - 1]));
                if (f[j][i] && maxLen < (i - j + 1)) {
                    maxLen = i - j + 1;
                    start = j;
                }
            }
        }
        System.out.println(s.substring(start, start + maxLen));
    }

    @DisplayName("中心扩散法-时间复杂度O(n^2)，空间复杂度O(n^2)")
    @ParameterizedTest
    @ValueSource(strings = {"AABSJDCNABBCCBBAD", "ABACBCABAB", "", "BC"})
    void longestPalindrome2(final String s) {
        if (s.length() < 2) {
            System.out.println(s);
            return;
        }
        // 保存起始位置，测试了用数组似乎能比全局变量稍快一点
        int[] range = new int[2];
        char[] str = s.toCharArray();
        for (int i = 0; i < s.length(); ++i) {
            // 把回文看成中间的部分全是同一字符，左右部分相对称
            // 找到下一个与当前字符不同的字符
            i = findLongest(str, i, range);
        }
        System.out.println(s.substring(range[0], range[1] + 1));
    }

    static int findLongest(char[] str, int low, int[] range) {
        //查找中间部分
        int high = low;
        while (high < str.length - 1 && str[high + 1] == str[low]) {
            ++high;
        }
        //定位中间部分的最后一个字符
        int ans = high;
        //从中间向左右扩散
        while (low > 0 && high < str.length - 1 && str[low - 1] == str[high + 1]) {
            --low;
            ++high;
        }
        //记录最大长度
        if ((high - low) > range[1] - range[0]) {
            range[0] = low;
            range[1] = high;
        }
        return ans;
    }
}
